﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public interface IScrollList
{
    int Count { get; }
    bool TryAddBefore(CreatorTool? creatorTool);
    bool TryAddNext(CreatorTool? creatorTool);
    void RefreshDisplayData();
    void RefreshLayout();
    void Update(out bool isSkipUpdate);
}

public struct ScrollListData<T>
{
    public int index;
    public T data;
    public RectTransform item;
}

public delegate void ItemHandler<T>(ref ScrollListData<T> data);

public class ScrollListInitData<T>
{
    public bool isLoopData;
    public ItemHandler<T> recycleHandler;
}

public class ScrollList<T> : IScrollList, IList<T>
{
    #region List 

    public T this[int index]
    {
        get
        {
            return m_Datas[index];
        }
        set
        {
            m_Datas[index] = value;
            TryReplaceItem(index);
        }
    }

    public int Count
    {
        get
        {
            return m_Datas.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public void Add(T item)
    {
        m_Datas.Add(item);
        TryAddNext();
    }

    public void AddRange(IEnumerable<T> collection)
    {
        m_Datas.AddRange(collection);
        RefreshLayout();
    }

    public void Clear()
    {
        TryClearItem();
    }

    public bool Contains(T item)
    {
        return m_Datas.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        m_Datas.CopyTo(array, arrayIndex);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return m_Datas.GetEnumerator();
    }

    public int IndexOf(T item)
    {
        return m_Datas.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        m_Datas.Insert(index, item);
        TryInsertItem(index);
    }

    public bool Remove(T item)
    {
        var index = m_Datas.IndexOf(item);
        RemoveAt(index);
        return index >= 0;
    }

    public void RemoveAt(int index)
    {
        if (index < 0 || index >= m_Datas.Count)
            return;

        TryRemoveItem(index);
        m_Datas.RemoveAt(index);
        RefreshLayout();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return m_Datas.GetEnumerator();
    }

    public T Find(Predicate<T> match)
    {
        return m_Datas.Find(match);
    }

    #endregion

    private ScrollRectEx scrollRectEx;
    public ScrollRectEx ScrollRectEx { get { return scrollRectEx; } }

    ItemHandler<T> m_initItemHandler;
    ItemHandler<T> m_recycleItemHandler;

    BaseScrollListCreator m_creator;
    public BaseScrollListCreator Creator { get { return m_creator; } }

    private List<T> m_Datas = new List<T>();
    public List<T> Datas
    {
        set
        {
            TryClearItem();
            m_Datas = value;
            RefreshLayout();
        }
    }

    private IndexData m_indexData;
    public IndexData IndexDataTool { get { return m_indexData; } }

    List<IScrollChecker> updateChecker = new List<IScrollChecker>();

    public bool isLoopData;
    private bool isNeedRefresh;

    public void Init(ScrollRectEx tool, ItemHandler<T> initHandler, ScrollListInitData<T> initData = null)
    {
        if (initData != null)
        {
            isLoopData = initData.isLoopData;
            m_indexData = initData.isLoopData ? new LoopIndexData() : new IndexData();
            m_recycleItemHandler = initData.recycleHandler;
        }
        else
        {
            m_indexData = new IndexData();
        }

        m_indexData.parent = this;

        scrollRectEx = tool;
        m_initItemHandler = initHandler;

        m_creator = GetLayout();
        if (m_creator == null)
            return;

        m_creator.Init(scrollRectEx, this);
        m_creator.m_lastItemChange = m_indexData.LastItemChange;
        m_creator.m_refreshItemImmetiate = RefreshItemImmetiate;
        m_creator.m_recycleItem = OnContentRecycleItem;

        TryClearItem();
    }

    public virtual bool IsBounds(Vector2 delta)
    {
        return true;
    }

    public virtual void OnValueChange(Vector2 delta)
    {
        if (m_creator == null)
            return;

        if (m_creator.IsMoveNextDirection(delta))
        {
            var creatorTool = CreatorToolEx.GetTool(CreatorTool.CreateType.Scroll);
            while (TryAddNext(creatorTool))
            {
                creatorTool.createIndex++;
            };
            return;
        }

        if (m_creator.IsMoveBeforeDirection(delta))
        {
            var creatorTool = CreatorToolEx.GetTool(CreatorTool.CreateType.Scroll);
            while (TryAddBefore(creatorTool))
            {
                creatorTool.createIndex++;
            };
            return;
        }
    }

    public virtual void OnViewBoundsChange()
    {
        if (m_creator != null)
            m_creator.OnViewBoundsChange();

        RefreshLayout();
    }

    public void Update(out bool isSkipUpdate)
    {
        if (isNeedRefresh)
            RefreshLayout();

        isSkipUpdate = false;
        if (updateChecker == null || updateChecker.Count == 0)
            return;

        List<IScrollChecker> removes = null;

        foreach (var checker in updateChecker)
        {
            if (checker.Check())
            {
                if (removes == null)
                    removes = new List<IScrollChecker>();
                removes.Add(checker);
            }

            var skipUpdater = checker as IScrollSkipUpdater;
            if (skipUpdater != null && skipUpdater.IsNeedSkip)
                isSkipUpdate = true;
        }

        if (removes != null)
        {
            foreach (var item in removes)
            {
                updateChecker.Remove(item);
            }
        }
    }

    /// <summary>
    /// 刷新当前正显示的数据
    /// </summary>
    public void RefreshDisplayData()
    {
        if (m_creator == null)
            return;

        m_indexData.ForFirstToLast((i, offset) =>
        {
            PreRefreshItem(i);

            RectTransform item = null;

            if (m_creator.TryGetItemByChildIndex(offset, out item))
                TryRefreshItem(i, item);

            return false;
        });
    }

    /// <summary>
    /// 替换数据，当前滚动的位置不变
    /// </summary>
    /// <param name="newDatas"></param>
    public void ReplaceData(List<T> newDatas)
    {
        if (m_creator == null)
            return;

        m_Datas = newDatas;

        // 如果新的数据比原来的数据少，移除多余的控件
        if (m_indexData.LastIndex >= m_Datas.Count)
        {
            var countOffset = m_indexData.LastIndex - m_Datas.Count + 1;
            if (countOffset > m_creator.activeList.Count)
            {
                m_indexData.LastIndex = m_Datas.Count - m_creator.activeList.Count - 1;
                m_creator.Clear();
            }
            else
            {
                for (int childOffset = 0; childOffset < countOffset && m_creator.activeList.Count > 0; childOffset++)
                    m_creator.TryRemoveItem(m_creator.activeList.Count - 1);
                m_indexData.LastIndex = m_Datas.Count - 1;
            }
        }

        RefreshLayout();

        m_indexData.ForFirstToLast((i, offset) =>
        {
            PreRefreshItem(i);

            RectTransform item = null;

            if (m_creator.TryGetItemByChildIndex(offset, out item))
                TryRefreshItem(i, item);

            return false;
        });
    }

    public void SetOrReplaceData(List<T> newDatas)
    {
        if (m_Datas == null)
            Datas = newDatas;
        else
            ReplaceData(newDatas);
    }

    /// <summary>
    /// 清除回收池
    /// </summary>
    public void ClearPool()
    {
        if (m_creator == null)
            return;
        m_creator.Clear(true);
    }

    /// <summary>
    /// 刷新布局
    /// </summary>
    public void RefreshLayout()
    {
        if (IsHide())
        {
            isNeedRefresh = true;
            return;
        }

        isNeedRefresh = false;

        if (m_creator == null)
            return;

        scrollRectEx.UpdateContent();

        while (TryAddNext()) { }
        while (TryAddBefore()) { }
    }

    /// <summary>
    /// 如果是隐藏，列表不会刷新
    /// </summary>
    /// <returns></returns>
    private bool IsHide()
    {
        if (!scrollRectEx.content.gameObject.activeInHierarchy)
            return true;

        return false;
    }

    public void ScrollCenterTo(int centerIndex, float smoothTime,
        bool isForbitReverse = false, Action onStopHandler = null, Action<int> OnCenterHandler = null)
    {
        var checker = new ScrollCenterChecker<T>(centerIndex, smoothTime,
            isForbitReverse, onStopHandler, OnCenterHandler, this);

        updateChecker.Add(checker);
    }

    public void LocateTo(int index)
    {
        if (index < 0 || index >= m_Datas.Count)
            return;

        if (m_creator == null)
            return;

        m_creator.Clear();
        ResetContent();

        m_indexData.LastIndex = index - 1;

        while (TryAddNext()) { }
        if (m_creator.IsNeedCreateNext())
        {
            m_creator.SetNormalizePosition(1);
            while (TryAddBefore()) { }
            // 判断前面够不够
            if (m_creator.IsNeedCreateBefore())
                m_creator.SetNormalizePosition(0);
        }
    }

    public bool TryAddNext(CreatorTool? creatorTool = null)
    {
        return TryCreateItem(m_indexData.NextIndex, true, creatorTool);
    }

    public bool TryAddBefore(CreatorTool? creatorTool = null)
    {
        return TryCreateItem(m_indexData.BeforeIndex, false, creatorTool);
    }

    protected virtual BaseScrollListCreator GetLayout()
    {
        var v = scrollRectEx.content.GetComponent<VerticalLayoutGroup>();
        if (v)
        {
            return new VerticalScrollListCreator();
        }

        var h = scrollRectEx.content.GetComponent<HorizontalLayoutGroup>();
        if (h)
        {
            return new HorizontalScrollListCreator();
        }

        var g = scrollRectEx.content.GetComponent<GridLayoutGroup>();
        if (g)
        {
            if (g.startAxis == GridLayoutGroup.Axis.Horizontal)
            {
                return new VerticalGridScrollListCreator();
            }
            else
            {
                return new HorizontalGridScrollListCreator();
            }
        }

        return new DefaultScrollListCreator();
    }

    private bool TryCreateItem(int index, bool isNext, CreatorTool? creatorTool = null)
    {
        if (m_creator == null)
            return false;

        if (IsHide())
        {
            isNeedRefresh = true;
            return false;
        }

        if (index >= m_Datas.Count || index < 0)
            return false;

        PreRefreshItem(index);

        RectTransform item = null;

        if (isNext ? m_creator.TryCreateNext(out item, creatorTool) :
           m_creator.TryCreateBefore(out item, creatorTool))
        {
            if (TryRefreshItem(index, item))
                scrollRectEx.UpdateContent();
            else
                scrollRectEx.UpdateBounds();

            return true;
        }

        return false;
    }

    private void TryReplaceItem(int index)
    {
        if (m_creator == null)
            return;

        // 检测该数据是否正在被显示

        if (!m_indexData.IsInView(index))
            return;

        PreRefreshItem(index);

        RectTransform item = null;
        var childIndex = m_indexData.GetChildIndex(index);
        if (!m_creator.TryGetItemByChildIndex(childIndex, out item))
            return;

        TryRefreshItem(index, item);
    }

    private void TryInsertItem(int index)
    {
        if (m_creator == null)
            return;

        if (IsHide())
        {
            isNeedRefresh = true;
            return;
        }

        if (index == m_indexData.NextIndex)
        {
            TryCreateItem(index, true);
            return;
        }

        if (index > m_indexData.LastIndex)
            return;

        if (m_indexData.IsBeforeFirstIndex(index))
        {
            m_indexData.LastItemChange(1);
            return;
        }

        PreRefreshItem(index);

        RectTransform item = null;

        var childIndex = m_indexData.GetChildIndex(index);
        if (!m_creator.TryInsertItem(childIndex, out item))
            return;

        TryRefreshItem(index, item);
    }

    private void TryRemoveItem(int index)
    {
        if (m_creator == null)
            return;

        if (index > m_indexData.LastIndex)
            return;

        if (m_indexData.IsBeforeFirstIndex(index))
        {
            m_indexData.LastItemChange(-1);
            return;
        }

        var childIndex = m_indexData.GetChildIndex(index);
        m_creator.TryRemoveItem(childIndex);
    }

    private void TryClearItem()
    {
        if (m_creator != null)
            m_creator.Clear();

        m_indexData.LastIndex = -1;
        m_Datas = new List<T>();

        ResetContent();
    }

    private void ResetContent()
    {
        scrollRectEx.ZeroContent();
        scrollRectEx.UpdateContent();
        scrollRectEx.StopMove();
    }

    private void WrapData(int index, RectTransform item, out ScrollListData<T> data)
    {
        data = new ScrollListData<T>();
        data.index = index;
        data.data = m_Datas[index];
        data.item = item;
    }

    private bool isHadRefresh;
    private int operationIndex;
    private void PreRefreshItem(int index)
    {
        isHadRefresh = false;
        operationIndex = index;
    }

    private bool TryRefreshItem(int index, RectTransform item)
    {
        if (isHadRefresh)
            return false;

        WrapData(index, item, out var data);
        m_initItemHandler(ref data);
        return true;
    }

    private void OnContentRecycleItem(int itemIndex, RectTransform item)
    {
        if (m_recycleItemHandler == null)
            return;

        WrapData(m_indexData.FirstIndex + itemIndex, item, out var data);
        m_recycleItemHandler(ref data);
    }

    private void RefreshItemImmetiate(RectTransform item)
    {
        TryRefreshItem(operationIndex, item);

        scrollRectEx.UpdateContent();

        isHadRefresh = true;
    }

    public class IndexData
    {
        public ScrollList<T> parent;

        public virtual int BeforeIndex
        {
            get
            {
                return FirstIndex - 1;
            }
        }

        public virtual int FirstIndex
        {
            get
            {
                if (parent.m_creator.activeList.Count == 0)
                    return LastIndex;

                return LastIndex - parent.m_creator.activeList.Count + 1;
            }
        }

        private int m_lastIndex;
        public virtual int LastIndex
        {
            get
            {
                return m_lastIndex;
            }
            set
            {
                if (value < -1)
                {
                    m_lastIndex = -1;
                }
                else if (value >= parent.m_Datas.Count)
                {
                    m_lastIndex = parent.m_Datas.Count - 1;
                }
                else
                {
                    m_lastIndex = value;
                }
            }
        }

        public virtual int NextIndex
        {
            get
            {
                return LastIndex + 1;
            }
        }

        public virtual void ForFirstToLast(Func<int, int, bool> handler)
        {
            for (int i = FirstIndex; i <= LastIndex; i++)
            {
                if (handler(i, i - FirstIndex))
                    return;
            }
        }

        public virtual int GetChildIndex(int index)
        {
            return index - FirstIndex;
        }

        public virtual bool IsBeforeFirstIndex(int index)
        {
            return index < FirstIndex;
        }

        public virtual void LastItemChange(int changeCount)
        {
            LastIndex += changeCount;
        }

        public virtual bool IsInView(int index)
        {
            if (index < FirstIndex || index > LastIndex)
                return false;
            return true;
        }
    }

    // 在一个显示段的数据索引不重复
    public class LoopIndexData : IndexData
    {
        public override int BeforeIndex
        {
            get
            {
                var index = FirstIndex - 1;
                if (index < 0)
                    return parent.m_Datas.Count - 1;
                return index;
            }
        }

        public override int FirstIndex
        {
            get
            {
                if (parent.m_creator.activeList.Count == 0)
                    return LastIndex;

                var index = LastIndex - parent.m_creator.activeList.Count + 1;
                if (index < 0)
                {
                    return parent.m_Datas.Count + index;
                }

                return index;
            }
        }

        private int m_lastIndex;
        public override int LastIndex
        {
            get
            {
                return m_lastIndex;
            }
            set
            {
                if (value < -1)
                {
                    m_lastIndex = parent.m_Datas.Count + value;
                }
                else if (value >= parent.m_Datas.Count)
                {
                    m_lastIndex = value % parent.m_Datas.Count;
                }
                else
                {
                    m_lastIndex = value;
                }
            }
        }

        public override int NextIndex
        {
            get
            {
                var index = LastIndex + 1;
                if (index >= parent.m_Datas.Count)
                    return 0;
                return index;
            }
        }

        private bool IsLink()
        {
            var index = LastIndex - parent.m_creator.activeList.Count + 1;
            return index < 0;
        }

        public override void ForFirstToLast(Func<int, int, bool> handler)
        {
            var firstIndex = FirstIndex;
            if (!IsLink())
            {
                base.ForFirstToLast(handler);
            }
            else
            {
                var count = parent.m_creator.activeList.Count;
                for (int i = 0; i < count; i++)
                {
                    var index = firstIndex + i;
                    if (index >= parent.m_Datas.Count)
                        index %= parent.m_Datas.Count;

                    if (handler(index, i))
                        return;
                }
            }
        }

        public override int GetChildIndex(int index)
        {
            var childIndex = base.GetChildIndex(index);
            if (childIndex < 0)
                childIndex = index + parent.m_Datas.Count - FirstIndex;

            return childIndex;
        }

        public override bool IsBeforeFirstIndex(int index)
        {
            return index < FirstIndex;
        }

        public override bool IsInView(int index)
        {
            if (!IsLink())
            {
                return base.IsInView(index);
            }
            else
            {
                if (FirstIndex <= index && index <= parent.m_Datas.Count - 1)
                    return true;
                if (0 <= index && index <= LastIndex)
                    return true;
                return false;
            }
        }
    }
}

